package com.declum.archon.chunkserver.manager.disk.streams;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;

/**
 * This class provide Output Stream for Chunks. It also calculates the Checksum
 * while flushing the data. Checksum will be calculated only of the Flush method
 * is called.
 * 
 * @author Clement
 */
public class ChunkOutputStream extends OutputStream {
	/**
	 * Holds the file instance of Chunk
	 */
	File chunkFile;
	/**
	 * Holds file instance of check sum file
	 */
	File checksumFile;

	/**
	 * Original Stream for file
	 */
	private FileOutputStream chunkOut;

	/**
	 * Size of Buffer
	 */
	private int bufferSize = 1024;

	/**
	 * Set buffer size
	 * 
	 * @param bufferSize
	 */
	public void setBufferSize(int bufferSize) {
		throw new RuntimeException();
		// this.bufferSize = bufferSize;
	}

	/**
	 * Create a Chunk output stream for the given chunkPath. Old data on the
	 * file will be overwritten
	 * 
	 * @param chunkPath
	 * @throws IOException
	 */
	public ChunkOutputStream(String chunkPath) throws IOException {
		this(chunkPath, false);
	}

	/**
	 * Create a Chunk output stream for the given chunkPath. Rewrite mode or
	 * Append mode is based on the flag provided
	 * 
	 * @param chunkPath
	 * @param flag
	 * @throws IOException
	 */
	public ChunkOutputStream(String chunkPath, boolean flag) throws IOException {
		this(new File(chunkPath), flag);
	}

	/**
	 * Create a Chunk output stream for the given file. Old data on the file
	 * will be overwritten
	 * 
	 * @param chunkFile
	 * @throws IOException
	 */
	public ChunkOutputStream(File chunkFile) throws IOException {
		this.loadChunkFile(chunkFile, false);
	}

	/**
	 * Create a Chunk output stream for the given file. Rewrite mode or Append
	 * mode is based on the flag provided
	 * 
	 * @param chunkFile
	 * @param flag
	 * @throws IOException
	 */
	public ChunkOutputStream(File chunkFile, boolean flag) throws IOException {
		this.loadChunkFile(chunkFile, flag);
	}

	/**
	 * 
	 * Loads the file output stream
	 */
	void loadChunkFile(File chunkFile, boolean flag) throws IOException {
		FileUtils.existsAndBeFile(chunkFile);
		this.chunkFile = chunkFile;
		this.chunkOut = new FileOutputStream(this.chunkFile, flag);
	}

	@Override
	public void write(int i) throws IOException {
		this.chunkOut.write(i);
	}

	@Override
	public void write(byte[] abyte0) throws IOException {
		this.chunkOut.write(abyte0);
	}

	@Override
	public void write(byte[] abyte0, int i, int j) throws IOException {
		this.chunkOut.write(abyte0, i, j);
	}

	@Override
	public void flush() throws IOException {
		this.chunkOut.flush();
		this.reCalculateCheckSum();
		this.loadChunkFile(this.chunkFile, true);
		super.flush();
	}

	private void reCalculateCheckSum() throws IOException {
		FileInputStream in = new FileInputStream(this.chunkFile);
		File checkSumFile = new File(FileUtils
				.getCheckSumFileNameFromChunkPath(this.chunkFile
						.getAbsolutePath()));

		FileOutputStream out = new FileOutputStream(checkSumFile);
		DataOutputStream outStream = new DataOutputStream(out);
		byte[] buffer = new byte[this.bufferSize];
		int readCount = 0;
		do {
			Arrays.fill(buffer, (byte) 0);
			readCount = this.fillBuffer(in, buffer);
			outStream.writeInt(FileUtils.getCheckSumFotBuffer(buffer));
		} while (readCount != -1);
		try {
			outStream.close();
			in.close();
		} catch (Exception e) {

		}
	}

	private int fillBuffer(FileInputStream in, byte[] buffer)
			throws IOException {
		int count = 0;
		int read = 0;
		while (count < buffer.length) {
			read = in.read(buffer, count, buffer.length - count);
			if (read < 0) {
				break;
			}
			count += read;
		}
		if ((count == 0) && (read < 0)) {
			return read;
		}
		return count;
	}

	@Override
	public void close() throws IOException {
		this.chunkOut.flush();
		this.flush();
		this.chunkOut.close();
		super.close();
	}
}
